/*
 * Copyright 2011-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.platform.core.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.regex.Pattern;

/**
 * 大数值的加、减、乘、除，四舍五入、取余、取模、开方，比较大小等运算。
 *
 * @author liym
 * @since 2015-10-25 21:02:02 新建
 */
public final class MathUtils {

    /**
     * 数字格式化，保留 4 位小数点
     */
    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("##,###,###.####");
    /**
     * {@code ,}的正则表达式
     */
    private static final Pattern COMMA_PATTERN = Pattern.compile(",");

    /**
     * {@code 0}
     */
    private static final String ZERO = "0";
    /**
     * {@code 2}
     */
    private static final BigDecimal TWO = new BigDecimal("2");

    private MathUtils() {
        throw new IllegalAccessError("Utility class");
    }

    /**
     * 将字符串表示的十进制数值转换成实数
     *
     * <p>可以包含小数点、正负号、千分符等</p>
     *
     * @param decimal 大数字
     * @return {@link BigDecimal}
     * @since 2014-06-18 14:06:25
     */
    private static BigDecimal toBigDecimal(final String decimal) {
        if (decimal == null) {
            throw new NumberFormatException("不是合法的数值：null");
        }

        String inputVal = COMMA_PATTERN.matcher(decimal).replaceAll("").trim();
        if (inputVal.isEmpty()) {
            // 改成 "0"，为了兼容 Java 1.4
            return new BigDecimal(ZERO);
        }
        BigDecimal retVal;
        try {
            retVal = new BigDecimal(inputVal);
            retVal = retVal.stripTrailingZeros();
        } catch (NumberFormatException e) {
            throw new NumberFormatException("不是合法的数值：" + decimal);
        }
        return retVal;
    }

    /**
     * 将字符串表示的十进制整数转换成整数
     *
     * <p>不能包含小数点，可以有正负号、千分符等</p>
     *
     * @param intVal 大整数
     * @return {@link BigInteger}
     * @author liym
     * @since 2015-02-02 22:09:02
     */
    private static BigInteger toBigInteger(final String intVal) {
        if (intVal == null) {
            throw new NumberFormatException("不是合法的整数：null");
        }

        String inputVal = COMMA_PATTERN.matcher(intVal).replaceAll("").trim();
        if (inputVal.isEmpty()) {
            return new BigInteger(ZERO);
        }
        BigInteger retVal;
        try {
            retVal = new BigInteger(inputVal);
        } catch (NumberFormatException e) {
            throw new NumberFormatException("不是合法的整数：" + intVal);
        }
        return retVal;
    }

    /**
     * 加
     *
     * <p>可以将带千分符的两个字段相加</p>
     *
     * @param x 加数（addend）
     * @param y 被加数（augend）
     * @return {@code x + y}
     */
    public static String add(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).add(toBigDecimal(y));
        return result.toString();
    }

    /**
     * 减
     *
     * @param x 被减数（minuend）
     * @param y 减数（subtrahend）
     * @return {@code x - y}
     */
    public static String subtract(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).subtract(toBigDecimal(y));
        return result.toString();
    }

    /**
     * 乘
     *
     * @param x 乘数（multiplier）
     * @param y 被乘数（multiplicand）
     * @return {@code x * y}
     */
    public static String multiply(final String x, final String y) {
        return multiply(x, y, 0);
    }

    /**
     * 乘
     *
     * <p>支持指定保留小数位数</p>
     *
     * @param x 乘数（multiplier）
     * @param y 被乘数（multiplicand）
     * @param scale 结果保留小数位数
     * @return {@code x * y}
     */
    public static String multiply(final String x, final String y, int scale) {
        BigDecimal multiplier = toBigDecimal(x);
        BigDecimal multiplicand = toBigDecimal(y);
        // 乘法结果的小数位数为乘数与被乘数小数位数的和
        BigDecimal result = multiplier.multiply(multiplicand);
        if (scale > 0) {
            result = result.setScale(scale, RoundingMode.HALF_UP);
        }
        return result.toString();
    }

    /**
     * 除
     * <p>结果保留2位小数</p>
     * <p>被除数÷除数=商（x÷y=z）</p>
     *
     * @param x 被除数（dividend）
     * @param y 除数（divisor）
     * @return {@code x / y}
     */
    public static String divide(final String x, final String y) {
        return divide(x, y, 2);
    }

    /**
     * 除
     * <p>支持指定保留小数位数</p>
     * <p>被除数÷除数=商（x÷y=z）</p>
     *
     * @param x 被除数（dividend）
     * @param y 除数（divisor）
     * @param scale 结果保留小数位数
     * @return {@code x / y}
     */
    public static String divide(final String x, final String y, int scale) {
        // 判断除数是否等于零
        BigDecimal divisor = toBigDecimal(y);
        if (divisor.signum() == 0) {
            throw new ArithmeticException("除数不能等于零（y = 0）");
        }
        BigDecimal dividend = toBigDecimal(x);

        // 如果保留小数位数小于零，计算结果将使用科学计数法表示 2016-05-20 11:12
        if (scale <= 0) {
            int scaleX = dividend.scale();
            int scaleY = divisor.scale();
            // 除法结果的小数位数取被除数与除数小数位数中更大的值
            scale = Math.max(scaleX, scaleY);
        }
        BigDecimal result = dividend.divide(divisor, scale, RoundingMode.HALF_UP);
        return result.toString();
    }

    /**
     * 四舍五入
     *
     * <p>支持指定保留小数位数</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return 四舍五入后的字符串
     */
    public static String round(final String x, int scale) {
        scale = scale < 0 ? 2 : scale;
        BigDecimal result = toBigDecimal(x).setScale(scale, RoundingMode.HALF_EVEN);
        return result.toString();
    }

    /**
     * 取余（remainder）
     *
     * <p>求余是指一个数除以另一个数，不够除的部分就是余数，就是求余的结果。</p>
     * <p>该方法不是模操作，即其结果可能是负的。</p>
     *
     * @param x 被除数（dividend）
     * @param y 除数（divisor）
     * @return {@code x % y}（余数）
     * @see BigInteger#mod(BigInteger)
     * @see #mod(String, String)
     */
    public static String remainder(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).remainder(toBigDecimal(y));
        return result.toString();
    }

    /**
     * 判断一个数是否是另一个数的倍数（is a multiple of）
     *
     * <p>整除就是一个数除以另一个数，刚刚好的倍数，这里没有四舍五入，不够一倍的都将舍去。</p>
     *
     * @param x 被除数（dividend）
     * @param y 除数（divisor）
     * @return {@code x % y == 0}（如果是整倍数，返回true）
     */
    public static boolean isMultiple(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).remainder(toBigDecimal(y));
        int sign = result.signum();
        return sign == 0;
    }

    /**
     * 判断一个数是否为2的幂次方
     *
     * @param x 待判断的数值
     * @return true/false
     * @since 2025-08-04 22:00
     */
    public static boolean isPowerOfTwo(int x) {
        return x > 0 && (x & (x - 1)) == 0;
    }

    /**
     * 判断一个数是否为2的幂次方
     *
     * @param x 待判断的数值
     * @return true/false
     * @since 2025-08-04 22:14
     */
    public static boolean isPowerOfTwo(long x) {
        return x > 0 && (x & (x - 1)) == 0;
    }

    /**
     * 比较大小
     *
     * @param x 数值1
     * @param y 数值2
     * @return 比较两个数值的大小：x﹦y返回0，x﹥y返回1，x﹤y返回-1
     */
    public static int compare(final String x, final String y) {
        return toBigDecimal(x).compareTo(toBigDecimal(y));
    }

    /**
     * 第一个数是否大于第二个数
     *
     * @param x 数值1
     * @param y 数值2
     * @return {@code x > y}
     * @version 2014-06-18 14:31:11
     */
    public static boolean isGreaterThan(final String x, final String y) {
        int result = compare(x, y);
        return result > 0;
    }

    /**
     * 比较：{@code val} 是否大于或等于零
     *
     * @param x 数值
     * @return {@code x >= 0}
     * @version 2014-06-18 14:32:16
     */
    public static boolean isGreaterThanZero(final String x) {
        BigDecimal decimal = toBigDecimal(x);
        int sign = decimal.signum();
        return sign >= 0;
    }

    /**
     * 取模（modulo）
     *
     * <p>求余与求摸的区别是，%所得的数有正有负，符号取决于左操作数，而mod只能是正数。</p>
     * <p>a mod b = (a % b + b) % b，括号里的目的是把左操作数转成正数</p>
     *
     * @param x 整数（integer）
     * @param y 模数（整数）
     * @return {@code x mod y}
     * @author liym
     * @see BigDecimal#remainder(BigDecimal)
     * @see #remainder(String, String)
     * @see <a href="http://acm.hdu.edu.cn/showproblem.php?pid=1104">http://acm.hdu.edu.cn/showproblem.php?pid=1104</a>
     * @since 2015-02-02 22:05:02
     */
    public static String mod(final String x, final String y) {
        BigInteger modulus = toBigInteger(y);
        if (modulus.signum() > 0) {
            BigInteger result = toBigInteger(x).mod(modulus);
            return result.toString();
        }
        throw new ArithmeticException("mod运算除数只能为正数（y < 0）");
    }

    /**
     * 左移
     *
     * <p>将 x 的值左移 n 位，n 应该为正数，否则，左移变成右移。</p>
     *
     * @param x 整数（integer）
     * @param n 左移的位数，应该大于零
     * @return {@code x << n}
     * @author liym
     * @see #shiftRight(String, int)
     * @since 2016-01-05 15:12:49
     */
    public static String shiftLeft(final String x, final int n) {
        BigInteger result = toBigInteger(x).shiftLeft(n);
        return result.toString();
    }

    /**
     * 右移
     *
     * <p>将 x 的值右移 n 位，n 应该为正数，否则，右移变成左移。</p>
     *
     * @param x 整数（integer）
     * @param n 右移的位数，应该大于零
     * @return {@code x >> n}
     * @author liym
     * @see #shiftLeft(String, int)
     * @since 2016-01-05 15:13:41
     */
    public static String shiftRight(final String x, final int n) {
        BigInteger result = toBigInteger(x).shiftRight(n);
        return result.toString();
    }

    /**
     * 乘方（幂）
     *
     * <p>求n个相同因数乘积的运算，叫做乘方，乘方的结果叫做幂（power）。</p>
     *
     * @param x 底数（base number）
     * @param n 指数（exponent）
     * @return {@code x}<sup>{@code n}</sup>（<tt>0<sup>0</sup></tt>=0）
     * @author liym
     * @version 2016-05-20 13:45:10
     */
    public static String pow(final String x, final int n) {
        final int maxPow = 999999999;
        if (n < 0 || n > maxPow) {
            throw new ArithmeticException("指数的值不合法。指数的取值范围为 [0~999999999)。");
        }
        BigDecimal result = toBigDecimal(x).pow(n);
        return result.toString();
    }

    /**
     * 平方根
     *
     * <p>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</p>
     *
     * <p>平方根（sqrt）：square rooting</p>
     * <p>立方根（cbrt）：cube rooting</p>
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @see Math#sqrt(double)
     * @see Math#cbrt(double)
     * @see BigDecimal#sqrt(MathContext)
     * @since 2016-06-01 09:06:31
     */
    public static String sqrt(final String x) {
        return sqrt(x, 0);
    }

    /**
     * 平方根
     *
     * <p>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</p>
     *
     * <p>平方根（sqrt）：square rooting</p>
     * <p>立方根（cbrt）：cube rooting</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return {@code sqrt(x)}
     * @author liym
     * @see Math#sqrt(double)
     * @see Math#cbrt(double)
     * @see BigDecimal#sqrt(MathContext)
     * @since 2024-09-18 12:31:55
     */
    public static String sqrt(final String x, int scale) {
        BigDecimal decimal = toBigDecimal(x);
        BigDecimal result = sqrt(decimal);
        if (scale > 0) {
            result = result.setScale(scale, RoundingMode.HALF_UP);
        }
        return result.toString();
    }

    /**
     * 平方根
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2016-06-01 09:06:31
     */
    public static String sqrt(final int x) {
        return sqrt(Integer.toString(x));
    }

    /**
     * 平方根
     * <p>支持指定保留小数位数</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-18 19:51:02
     */
    public static String sqrt(final int x, int scale) {
        return sqrt(Integer.toString(x), scale);
    }

    /**
     * 平方根
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2016-06-01 09:11:54
     */
    public static String sqrt(final long x) {
        return sqrt(Long.toString(x));
    }

    /**
     * 平方根
     * <p>支持指定保留小数位数</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-18 19:56:10
     */
    public static String sqrt(final long x, int scale) {
        return sqrt(Long.toString(x), scale);
    }

    /**
     * 平方根
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-14 21:11:35
     */
    public static String sqrt(final float x) {
        return sqrt(Float.toString(x));
    }

    /**
     * 平方根
     * <p>支持指定保留小数位数</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-18 20:00:27
     */
    public static String sqrt(final float x, int scale) {
        return sqrt(Float.toString(x), scale);
    }

    /**
     * 平方根
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-14 22:12:05
     */
    public static String sqrt(final double x) {
        return sqrt(Double.toString(x));
    }

    /**
     * 平方根
     * <p>支持指定保留小数位数</p>
     *
     * @param x 数值
     * @param scale 结果保留小数位数
     * @return {@code sqrt(x)}
     * @author liym
     * @since 2024-09-18 20:01:08
     */
    public static String sqrt(final double x, int scale) {
        return sqrt(Double.toString(x), scale);
    }

    /**
     * 平方根
     *
     * @param x 数值
     * @return {@code sqrt(x)}
     * @author liym
     * @version 2016-06-01 09:06:13
     * @see BigDecimal#sqrt(MathContext)
     */
    private static BigDecimal sqrt(final BigDecimal x) {
        final double zero = 0.0D;

        int precision = x.precision();
        double inputNum = x.doubleValue();
        if (precision == 0) {
            throw new IllegalArgumentException("Roots need a MathContext precision > 0");
        }
        if (inputNum < zero) {
            throw new ArithmeticException("Cannot calculate the square root of a negative number");
        }
        if (inputNum == zero) {
            return BigDecimal.ZERO;
        }
        if (precision < 50) {
            precision = precision * 2 + 10;
        }

        int setPrecision = 1;
        BigDecimal iter1, iter2; // 迭代

        if (inputNum == Double.POSITIVE_INFINITY) {
            BigInteger unscaledVal = x.unscaledValue();
            int bitLen = unscaledVal.bitLength();
            int bitSqrtLen = bitLen / 2;

            unscaledVal = unscaledVal.shiftRight(bitSqrtLen);
            iter1 = new BigDecimal(unscaledVal);

            MathContext tmpMc = new MathContext(5, RoundingMode.HALF_DOWN);
            iter2 = BigDecimal.ONE.divide(TWO.multiply(iter1, tmpMc), tmpMc);

            precision += 10;
        } else {
            double sqrtRet = Math.sqrt(inputNum);
            iter1 = new BigDecimal(Double.toString(sqrtRet));
            iter2 = new BigDecimal(Double.toString(0.5D / sqrtRet));

            // 保留 64 位数字
            setPrecision = 64;
        }

        MathContext mc = new MathContext(setPrecision, RoundingMode.HALF_UP);
        return sqrt0(x, mc, precision, iter1, iter2);
    }

    private static BigDecimal sqrt0(BigDecimal numberToSqrt, MathContext mc,
            final int precision, BigDecimal iter1, BigDecimal iter2) {
        BigDecimal temp1 = BigDecimal.ONE.subtract(TWO.multiply(iter1, mc).multiply(iter2, mc), mc);
        iter2 = iter2.add(temp1.multiply(iter2, mc), mc);

        BigDecimal temp2 = numberToSqrt.subtract(iter1.multiply(iter1, mc), mc);
        iter1 = iter1.add(temp2.multiply(iter2, mc), mc);

        int retPrecision = mc.getPrecision();
        if (retPrecision < 2) {
            retPrecision++;
        } else {
            retPrecision = retPrecision * 2 - 1;
        }
        if (retPrecision < (2 * precision)) {
            mc = new MathContext(retPrecision, mc.getRoundingMode());
            sqrt0(numberToSqrt, mc, precision, iter1, iter2);
        }
        return iter1;
    }

    // https://www.cnblogs.com/oasisyang/p/13207384.html
    // https://zh.wikipedia.org/wiki/平方根倒数速算法

    /**
     * 快速计算平方根倒数（Fast Inverse Square Root）
     *
     * @param x 底数值
     * @param iterNum 跌代次数（牛顿迭代法）
     * @return {@code 1 / sqrt(x)}
     * @since 2024-09-14 20:25
     */
    public static float fastInvSqrt(float x, int iterNum) {
        int i = Float.floatToIntBits(x);
        i = 0x5f375a86 - (i >> 1); // 估算平方根倒数  0x5f3759df
        float y = Float.intBitsToFloat(i);
        final float xHalf = x * 0.5F;
        for (int k = 0; k <= iterNum; k++) { // 迭代的次数越多，精度越高
            y = y * (1.5F - xHalf * y * y); // 牛顿迭代法
            y = y * (1.5F - xHalf * y * y);
            y = y * (1.5F - xHalf * y * y);
        }
        return y;
    }

    /**
     * 快速计算平方根倒数（Fast Inverse Square Root）
     *
     * @param x 底数值
     * @param iterNum 跌代次数（牛顿迭代法）
     * @return {@code 1 / sqrt(x)}
     * @since 2024-09-15 14:43:58
     */
    public static double fastInvSqrt(double x, int iterNum) {
        long i = Double.doubleToLongBits(x);
        i = 0x5fe6eb50c7b537a9L - (i >> 1); // 估算平方根倒数  0x5fe6ec85e7de30daL
        double y = Double.longBitsToDouble(i);
        final double xHalf = x * 0.5D;
        for (int k = 0; k <= iterNum; k++) { // 迭代的次数越多，精度越高
            y = y * (1.5F - xHalf * y * y); // 牛顿迭代法
            y = y * (1.5F - xHalf * y * y);
            y = y * (1.5F - xHalf * y * y);
        }
        return y;
    }

    /**
     * 数字格式化
     *
     * <p>用千分号分割，保留四位小数点</p>
     *
     * <p>4595305491221.93404 ==> 4,595,305,491,221.934</p>
     *
     * @param x 要格式化的数字
     * @return 格式化后的字符串
     * @author liym
     * @since 2016-07-07 11:27:30
     */
    public static String format(String x) {
        BigDecimal number = new BigDecimal(x);
        return DECIMAL_FORMAT.format(number);
    }

    /**
     * 数字格式化
     *
     * <p>用千分号分割，保留四位小数点</p>
     *
     * <p>4595305491221.93404 ==> 4,595,305,491,221.934</p>
     *
     * @param x 要格式化的数字
     * @return 格式化后的字符串
     * @author liym
     * @since 2016-07-07 11:36:11
     */
    public static String format(double x) {
        BigDecimal number = new BigDecimal(x);
        return DECIMAL_FORMAT.format(number);
    }

    /**
     * 数字格式化
     *
     * <p>用千分号分割，保留四位小数点</p>
     *
     * <p>4595305491221L ==> 4,595,305,491,221.934</p>
     *
     * @param x 要格式化的数字
     * @return 格式化后的字符串
     * @author liym
     * @since 2016-07-07 11:37:23
     */
    public static String format(long x) {
        BigDecimal number = new BigDecimal(x);
        return DECIMAL_FORMAT.format(number);
    }

    /**
     * 判断一个数值类型的数据是否为 Null
     *
     * @param number Integer、Long 等类型的数据
     * @return true/false
     * @since 2019-03-19 14:09
     */
    public static boolean isNull(Number number) {
        return number == null;
    }

    /**
     * 判断一个数值类型的数据是否为 Null 或 0
     *
     * @param number Integer、Long、Double 等类型的数据
     * @return true/false
     * @see Long#toString()
     * @see Double#toString()
     * @see Byte#toString()
     * @see #isNullOrZero(BigInteger)
     * @see #isNullOrZero(BigDecimal)
     * @since 2019-03-19 14:11
     */
    public static boolean isNullOrZero(Number number) {
        if (number == null) {
            return true;
        }
        String val = number.toString();
        return "0".equals(val) || "0.0".equals(val);
    }

    /**
     * 判断一个数值类型的数据是否为 Null 或 0
     *
     * @param number {@link BigInteger} 类型的数据
     * @return true/false
     * @see BigInteger#signum()
     * @see #isNullOrZero(Number)
     * @see #isNullOrZero(BigDecimal)
     * @since 2024-09-16 18:47:45
     */
    public static boolean isNullOrZero(BigInteger number) {
        if (number == null) {
            return true;
        }
        int sign = number.signum();
        return sign == 0;
    }

    /**
     * 判断一个数值类型的数据是否为 Null 或 0
     *
     * @param number {@link BigDecimal} 类型的数据
     * @return true/false
     * @see BigDecimal#signum()
     * @see #isNullOrZero(Number)
     * @see #isNullOrZero(BigInteger)
     * @since 2019-11-12 21:09
     */
    public static boolean isNullOrZero(BigDecimal number) {
        if (number == null) {
            return true;
        }
        int sign = number.signum();
        return sign == 0;
    }

    /**
     * 将字符串转换成 int 类型，默认为 0。
     *
     * @param str numerical str
     * @return int
     * @author liym
     * @version 2016-04-28 11:21:36
     */
    public static int safeToInt(Object str) {
        String ss = String.valueOf(str);
        int val = 0;
        try {
            val = Integer.parseInt(ss);
        } catch (NumberFormatException ignored) {
        }
        return val;
    }

    /**
     * 将字符串转换成 {@link Integer} 类型，默认为null。
     *
     * @param str numerical str
     * @return int
     * @author liym
     * @version 2016-04-28 11:23:32
     */
    public static Integer safeToInteger(Object str) {
        String ss = String.valueOf(str);
        Integer val;
        try {
            val = Integer.parseInt(ss);
        } catch (NumberFormatException e) {
            val = null;
        }
        return val;
    }

}
